﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NGS.BioCompactString
{
    public class CompactStringD
    {
        public char[] Chars;
        public short RawLength;
        public short NCount;
        //
        public CompactStringD(char[] arr)
        {//Our simple deserialization
            Chars = new char[arr.Length - 2];
            Array.Copy(arr, Chars, Chars.Length);
            RawLength = Convert.ToInt16(arr[Chars.Length]);
            NCount = Convert.ToInt16(arr[Chars.Length + 1]);
        }
        public CompactStringD(char[] arr, short len)
        {
            RawLength = len;
            Chars = arr;
            ComputeNCount();
        }
        public CompactStringD(string sequence)
        {
            RawLength = (short)sequence.Length;
            int nCompactLen = (RawLength + 5) / 6;
            Chars = new char[nCompactLen];

            NCount = 0;
            int nCurCondon = 0;
            for (int i = 0; i < nCompactLen; i++)
            {
                int compact = 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++)//3125=5^5
                {
                    switch (sequence[nCurCondon])
                    {
                        case 'C'://'C'=0x43
                            compact += 1 * j; break;
                        case 'G'://'G'=0x47
                            compact += 2 * j; break;
                        case 'T'://'T'=0x54
                            compact += 3 * j; break;
                        case 'N'://'N'=0x4E
                            compact += 4 * j; NCount++; break;
                        case 'c'://'C'=0x43
                            compact += 1 * j; break;
                        case 'g'://'G'=0x47
                            compact += 2 * j; break;
                        case 't'://'T'=0x54
                            compact += 3 * j; break;
                        case 'n'://'N'=0x4E
                            compact += 4 * j; NCount++; break;
                        //case 'A': do nothing
                        //case 'a': do nothing
                    }
                }
                Chars[i] = (char)compact;
            }
        }
        public CompactStringD(byte[] buff)
        {
            RawLength = (short)buff.Length;
            int nCompactLen = (RawLength + 5) / 6;
            Chars = new char[nCompactLen];
            NCount = 0;
            int nCurCondon = 0;
            for (int i = 0; i < nCompactLen; i++)
            {
                int compact = 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++)//3125=5^5
                {
                    switch (buff[nCurCondon])
                    {
                        case 67://'C'=0x43
                            compact += 1 * j; break;
                        case 71://'G'=0x47
                            compact += 2 * j; break;
                        case 84://'T'=0x54
                            compact += 3 * j; break;
                        case 78://'N'=0x4E
                            compact += 4 * j; NCount++; break;
                        case 99://'c'=0x63
                            compact += 1 * j; break;
                        case 103://'g'=0x67
                            compact += 2 * j; break;
                        case 116://'t'=0x74
                            compact += 3 * j; break;
                        case 110://'n'=0x6E
                            compact += 4 * j; NCount++; break;
                        //case 'A': do nothing
                        //case 'a': do nothing
                    }
                }
                Chars[i] = (char)compact;
            }
        }
        public override int GetHashCode()
        {
            if (Chars.Length < 2)
                return 12223 * Chars[0];
            return 11139 * Chars[Chars.Length - 1] + (int)Chars[Chars.Length - 2] - 31;
        }
        public int GetBasecode(int mer)//第幾個MER的值0~4 01234 ACGTN 
        {
            int arrPos = mer / 6;
            int charPos = mer - arrPos * 6;
            int compact = ((int)Chars[arrPos]) - 31;
            switch (charPos)
            {
                case 0:
                    return compact / 3125;
                case 1:
                    compact %= 3125;
                    return compact / 625;
                case 2:
                    compact %= 625;
                    return compact / 125;
                case 3:
                    compact %= 125;
                    return compact / 25;
                case 4:
                    compact %= 25;
                    return compact / 5;
                case 5:
                    compact %= 5;
                    return compact;
            }
            return 0;
        }
        public byte[] ToByteArray()
        {
            byte[] barr = new byte[RawLength];

            int nCurCondon = 0;
            for (int i = 0; i < Chars.Length; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++)//3125=5^5
                {
                    int nX = compact / j;
                    switch (nX)
                    {
                        case 1://'C'=0x43
                            barr[nCurCondon] = 67;
                            break;
                        case 2://'G'=0x47
                            barr[nCurCondon] = 71;
                            break;
                        case 3://'T'=0x54
                            barr[nCurCondon] = 84;
                            break;
                        case 4://'N'=0x4E
                            barr[nCurCondon] = 78;
                            break;
                        case 0://'A'=0x41
                            barr[nCurCondon] = 65;
                            break;
                    }
                    compact -= nX * j;
                }
            }
            return barr;
        }
        public char[] ToCharArray()
        {
            char[] carr = new char[RawLength];
            int nCurCondon = 0;
            for (int i = 0; i < Chars.Length; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++)//3125=5^5
                {
                    int nX = compact / j;
                    switch (nX)
                    {
                        case 1://'C'=0x43
                            carr[nCurCondon] = 'C';
                            break;
                        case 2://'G'=0x47
                            carr[nCurCondon] = 'G';
                            break;
                        case 3://'T'=0x54
                            carr[nCurCondon] = 'T';
                            break;
                        case 4://'N'=0x4E
                            carr[nCurCondon] = 'N';
                            break;
                        case 0://'A'=0x41
                            carr[nCurCondon] = 'A';
                            break;
                    }
                    compact -= nX * j;
                }
            }
            return carr;
        }
        public string CodedString()
        {
            return new string(CodedChars());
        }
        public char[] CodedChars()
        {////Our simple serialization
            char[] codedArray = new char[Chars.Length + 2];
            Array.Copy(Chars, codedArray, Chars.Length);
            codedArray[Chars.Length] = Convert.ToChar(RawLength);
            codedArray[Chars.Length + 1] = Convert.ToChar(NCount);
            return codedArray;
        }
        public override string ToString()
        {
            return new string(ToCharArray());
        }
        public CompactStringD CompactHead(int nLength)
        {
            int nCompactLen = (nLength + 5) / 6;
            char[] newChars = new char[nCompactLen];
            if (nCompactLen > 1)
            {
                for (int i = 0; i < nCompactLen - 1; i++)
                    newChars[i] = Chars[i];
            }
            int nCurCondon = (nCompactLen - 1) * 6;
            int compact = 31;
            for (int j = 3125; j >= 1 && nCurCondon < nLength; j /= 5, nCurCondon++)//3125=5^5
            {
                int nX = GetBasecode(nCurCondon);
                compact += nX * j;
            }
            newChars[nCompactLen - 1] = (char)compact;
            return new CompactStringD(newChars, (short)nLength);
        }
        public CompactStringD CompactTail(int nLength)
        {
            int nCompactLen = (nLength + 5) / 6;
            char[] newChars = new char[nCompactLen];
            int nCurCondon = 0;
            int nOldCondon = RawLength - nLength;
            for (int i = 0; i < nCompactLen; i++)
            {
                int compact = 31;
                for (int j = 3125; j >= 1 && nCurCondon < nLength; j /= 5, nCurCondon++, nOldCondon++)//3125=5^5
                {
                    int nX = GetBasecode(nOldCondon);
                    compact += nX * j;
                }
                newChars[i] = (char)compact;
            }
            return new CompactStringD(newChars, (short)nLength);
        }
        public char[] HeadArray(int nLength)
        {
            char[] carr = new char[nLength];
            int nCurCondon = 0;
            int nGoto = (nLength - 1) / 6;//最後的charater

            for (int i = 0; i <= nGoto; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1 && nCurCondon < nLength; j /= 5, nCurCondon++)//3125=5^5
                {
                    int nX = compact / j;
                    switch (nX)
                    {
                        case 1://'C'=0x43
                            carr[nCurCondon] = 'C';
                            break;
                        case 2://'G'=0x47
                            carr[nCurCondon] = 'G';
                            break;
                        case 3://'T'=0x54
                            carr[nCurCondon] = 'T';
                            break;
                        case 4://'N'=0x4E
                            carr[nCurCondon] = 'N';
                            break;
                        case 0://'A'=0x41
                            carr[nCurCondon] = 'A';
                            break;
                    }
                    compact -= nX * j;
                }
            }
            return carr;
        }
        //HeadCode() 2010/10/27 Version: 1.0.0
        public long HeadCode(int nCodeLen)
        {
            //if (Length < nCodeLen || NCount > 0) return -1;

            long Code = 0;
            int nCurCondon = 0;
            int nGoto = (nCodeLen - 1) / 6;//最後的charater

            for (int i = 0; i <= nGoto; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1 && nCurCondon < nCodeLen; j /= 5, nCurCondon++)//3125=5^5
                {
                    int nX = compact / j;
                    Code *= 4;
                    if (nX == 4)
                        return -1;
                    Code += nX;
                    compact -= nX * j;
                }
            }
            return Code;
        }
        public long TailCode(int nCodeLen)
        {
            //if (Length < nCodeLen || NCount > 0) return -1;

            long Code = 0;

            int nStart = RawLength - nCodeLen;
            int nOffset = nStart / 6;//開始的charater
            int nCurCondon = nOffset * 6;
            for (int i = nOffset; i < Chars.Length; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++)
                {
                    int nX = compact / j;
                    if (nCurCondon >= nStart)
                    {
                        if (nX == 4)
                            return -1;
                        Code *= 4;
                        Code += nX;
                    }
                    compact -= nX * j;
                }
            }
            return Code;
        }
        public string Head(int nLength)
        {
            return new string(HeadArray(nLength));
        }
        public char[] TailArray(int nLength)
        {
            char[] carr = new char[nLength];

            int nStart = RawLength - nLength;
            int nOffset = nStart / 6;//開始的charater
            int nCurCondon = nOffset * 6, nPos = 0;
            for (int i = nOffset; i < Chars.Length; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++)
                {
                    int nX = compact / j;
                    compact -= nX * j;
                    if (nCurCondon >= nStart)
                    {
                        switch (nX)
                        {
                            case 1://'C'=0x43
                                carr[nPos++] = 'C';
                                break;
                            case 2://'G'=0x47
                                carr[nPos++] = 'G';
                                break;
                            case 3://'T'=0x54
                                carr[nPos++] = 'T';
                                break;
                            case 4://'N'=0x4E
                                carr[nPos++] = 'N';
                                break;
                            case 0://'A'=0x41
                                carr[nPos++] = 'A';
                                break;
                        }
                    }
                }
            }
            return carr;
        }
        public string Tail(int nLength)
        {
            return new string(TailArray(nLength));
        }
        public string Substring(int nStart, int nLen)
        {
            char[] carr = new char[nLen];

            int nEnd = nStart + nLen - 1;
            int nOffset = nStart / 6;//開始的charater
            int nGoto = nEnd / 6;//最後的charater
            int nCurCondon = nOffset * 6, nPos = 0;
            for (int i = nOffset; i <= nGoto; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1; j /= 5, nCurCondon++)
                {
                    if (nCurCondon > nEnd) break;
                    int nX = compact / j;
                    compact -= nX * j;
                    if (nCurCondon >= nStart)
                    {
                        switch (nX)
                        {
                            case 1://'C'=0x43
                                carr[nPos++] = 'C';
                                break;
                            case 2://'G'=0x47
                                carr[nPos++] = 'G';
                                break;
                            case 3://'T'=0x54
                                carr[nPos++] = 'T';
                                break;
                            case 4://'N'=0x4E
                                carr[nPos++] = 'N';
                                break;
                            case 0://'A'=0x41
                                carr[nPos++] = 'A';
                                break;
                        }
                    }
                }
            }
            return new string(carr);
        }
        public string Substring(int nStart)
        {
            if (nStart >= 0) //+從前面數
                return Substring(nStart, RawLength - nStart);
            else//-從後面數(nStart是負數)
                return Substring(0, RawLength + nStart);
        }

        public CompactStringD Reverse()
        {
            char[] newChars = new char[Chars.Length];
            int nOldCondon = RawLength - 1;
            int nCurCondon = 0;
            for (int i = 0; i < Chars.Length; i++)
            {
                int compact = 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++, nOldCondon--)//3125=5^5
                {
                    int nX = GetBasecode(nOldCondon);
                    compact += nX * j;
                }
                newChars[i] = (char)compact;
            }
            return new CompactStringD(newChars, RawLength);
        }
        public CompactStringD ReverseComplement()
        {
            char[] newChars = new char[Chars.Length];
            int nOldCondon = RawLength - 1;
            int nCurCondon = 0;
            for (int i = 0; i < Chars.Length; i++)
            {
                int compact = 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++, nOldCondon--)//3125=5^5
                {
                    int nX = GetBasecode(nOldCondon);
                    switch (nX)
                    {
                        case 1://'C'=0x43
                            compact += 2 * j; break;
                        case 2://'G'=0x47
                            compact += 1 * j; break;
                        case 3://'T'=0x54
                            break;
                        case 4://'N'=0x4E
                            compact += 4 * j; break;
                        case 0://'A'=0x41
                            compact += 3 * j; break;
                    }
                }
                newChars[i] = (char)compact;
            }
            return new CompactStringD(newChars, RawLength);
        }
        public int Length
        {
            get
            {
                return RawLength;
            }
        }
        private void ComputeNCount()
        {
            NCount = 0;
            int nCurCondon = 0;
            for (int i = 0; i < Chars.Length; i++)
            {
                int compact = ((int)Chars[i]) - 31;
                for (int j = 3125; j >= 1 && nCurCondon < RawLength; j /= 5, nCurCondon++)//3125=5^5
                {
                    int nX = compact / j;
                    if (nX == 4) //'N'
                        NCount++;
                    compact -= nX * j;
                }
            }
        }
    }
    public class CompactEqualityComparer : IEqualityComparer<CompactStringD>
    {
        public bool Equals(CompactStringD x, CompactStringD y)
        {
            for (int i = 0; i < x.Chars.Length; i++)
            {
                if (x.Chars[i] != y.Chars[i]) return false;
            }
            return true;
        }
        public int GetHashCode(CompactStringD x)
        {
            return x.GetHashCode();
        }

    }
    public class CompactComparer : IComparer<CompactStringD>
    {
        public int Compare(CompactStringD x, CompactStringD y)
        {
            for (int i = 0; i < x.Chars.Length; i++)
            {
                if (x.Chars[i] < y.Chars[i]) return -1;
                if (x.Chars[i] > y.Chars[i]) return 1;
            }
            return 0;
        }
    }
}
